package com.liruo.learn.spring.mvc.config;

import org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.web.*;
import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @Author:liruo
 * @Date:2023-05-10-16:58:34
 * @Desc
 *
 * WebMvcEndpointManagementContextConfiguration
 * org.springframework.boot.actuate.autoconfigure.health.HealthEndpointWebExtensionConfiguration.MvcAdditionalHealthEndpointPathsConfiguration#healthEndpointWebMvcHandlerMapping(org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier, org.springframework.boot.actuate.health.HealthEndpointGroups)
 *
 * 报错的根本原因是: swagger
 * 在springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider#requestHandlers()
 * 中使用Orderings.byPatternsCondition()对所有handlerMapping的所有RequestMappingInfo进行排序
 *      因此RequestMappingInfo中必须包含PatternsRequestCondition,
 *      即mvc的配置中, 创建RequestMappingInfoHandlerMapping的子类中, 必须传PathPatternParser=null
 *          这是因为比如AbstractWebMvcEndpointHandlerMapping,RequestMappingHandlerMapping等这些子类一般会有一个PathPatternParser参数
 *              这个是AbstractHandlerMapping的属性patternParser
 *          那些子类一般会实现afterPropertiesSet方法, 这之中会创建一个RequestMappingInfo.BuilderConfiguration
 *                 有patternParser就设置patternParser, 没有就设置pathMatcher=null
 *          在创建RequestInfoMapping中, 会用RequestMappingInfo.BuilderConfiguration作为options
 *              比如RequestMappingInfo.paths(new String[]{this.endpointMapping.createSubPath(path)}).options(this.builderConfig)...
 *                  (在可能叫createRequestMappingInfo的方法中)
 *               在build方法中, options.patternParser!=null就设置PathPatternsRequestCondition,没有就设置PatternsRequestCondition(springfox使用这个)
 * 而actuator的自动配置类中
 *  org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration#webEndpointServletHandlerMapping(org.springframework.boot.actuate.endpoint.web.WebEndpointsSupplier, org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier, org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpointsSupplier, org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes, org.springframework.boot.actuate.autoconfigure.endpoint.web.CorsEndpointProperties, org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties, org.springframework.core.env.Environment)
 *  会使用new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, WebMvcAutoConfiguration.pathPatternParser)
 *  使用了WebMvcAutoConfiguration.pathPatternParser
 *  因此pathPatternParser!=null, 那么ant模式的PatternsRequestCondition就会被设置为null, 因此会报空指针异常
 *
 *  解决方法:
 *      因为WebMvcEndpointManagementContextConfiguration#webEndpointServletHandlerMapping
 *      使用了@ConditionalOnMissingBean, 因此可以自己创建, 那么只需要修改传参数pathPatternParser=null即可
 *      WebMvcEndpointManagementContextConfiguration中还有其他的handlerMapping, 那些都是pathPatternParser=null
 */
@Configuration
public class SwaggerAndActuatorConflictConfig {

    /**
     * 解决springboot升到2.6.x之后，knife4j报错
     *
     * @param webEndpointsSupplier        the web endpoints supplier
     * @param servletEndpointsSupplier    the servlet endpoints supplier
     * @param controllerEndpointsSupplier the controller endpoints supplier
     * @param endpointMediaTypes          the endpoint media types
     * @param corsEndpointProperties      the cors properties
     * @param webEndpointProperties       the web endpoints properties
     * @param environment                 the environment
     * @return the web mvc endpoint handler mapping
     */
    @Bean
    public WebMvcEndpointHandlerMapping webMvcEndpointHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
                                                                     ServletEndpointsSupplier servletEndpointsSupplier,
                                                                     ControllerEndpointsSupplier controllerEndpointsSupplier,
                                                                     EndpointMediaTypes endpointMediaTypes,
                                                                     CorsEndpointProperties corsEndpointProperties,
                                                                     WebEndpointProperties webEndpointProperties,
                                                                     Environment environment) {
        List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
        Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
        allEndpoints.addAll(webEndpoints);
        allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
        allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
        String basePath = webEndpointProperties.getBasePath();

        return new WebMvcEndpointHandlerMapping(new EndpointMapping(basePath),
                webEndpoints,
                endpointMediaTypes,
                corsEndpointProperties.toCorsConfiguration(),
                new EndpointLinksResolver(allEndpoints, basePath),
                shouldRegisterLinksMapping(webEndpointProperties, environment, basePath),
                null);
    }

    /**
     * shouldRegisterLinksMapping
     *
     * @param webEndpointProperties
     * @param environment
     * @param basePath
     * @return
     */
    private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
        return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
    }

}
